from re import search

import pytomlpp
import os
import threading
import subprocess
import platform
import sys

sys.path.append('..')
from Plugins.tools.EventTools import EventHandler
from Plugins.tools.InfoClasses import MSLXEvents, PluginInfo, EventHandlerInfo
from Plugins.tools.PluginTools import AddPluginInfo
from Plugins.tools.lib import logger


class JavaSearchThread(threading.Thread):
    def __init__(self, path, result_list, lock, priority_keywords=None, strict_mode=False, excluded_keywords=None,
                 excluded_dirs=None, max_depth=None, depth_threshold=5, thread_pool=None):
        threading.Thread.__init__(self)
        self.path = path
        self.result_list = result_list
        self.lock = lock
        self.priority_keywords = priority_keywords if priority_keywords else []
        self.strict_mode = strict_mode
        self.excluded_keywords = excluded_keywords if excluded_keywords else []
        self.excluded_dirs = excluded_dirs if excluded_dirs else []
        self.max_depth = max_depth
        self.depth_threshold = depth_threshold
        self.thread_pool = thread_pool

    def walk_with_depth(self, top, current_depth):
        """Generator to walk directory with depth limit."""
        for root, dirs, files in os.walk(top):
            depth = root[len(top):].count(os.sep)
            if self.max_depth is not None and depth >= self.max_depth:
                dirs[:] = []
            yield root, dirs, files
            if current_depth >= self.depth_threshold:
                # Exceeded depth threshold, start a new thread for remaining top-level directories
                with self.lock:
                    if self.thread_pool:
                        new_thread = self.thread_pool.pop()
                        new_thread.path = root
                        new_thread.start()
                return

    def search_dirs(self, path, current_depth=0):
        try:
            for root, dirs, files in self.walk_with_depth(path, current_depth):
                # 排除指定的目录
                dirs[:] = [d for d in dirs if os.path.join(root, d) not in self.excluded_dirs]
                # 排除包含指定关键字的目录
                dirs[:] = [d for d in dirs if not any(keyword.lower() in d.lower()
                                                      for keyword in self.excluded_keywords)]

                # 如果只搜索包含关键字的目录
                if self.strict_mode:
                    dirs[:] = [d for d in dirs if any(keyword.lower() in d.lower()
                                                      for keyword in self.priority_keywords)]
                else:
                    # 优先搜索包含指定关键词的目录
                    dirs.sort(key=lambda d: any(keyword.lower() in d.lower()
                                                for keyword in self.priority_keywords), reverse=True)

                for file in files:
                    if file == "java" or file == "java.exe":
                        java_path = os.path.join(root, file)
                        version = self.check_java_version(java_path)
                        if version:
                            with self.lock:
                                self.result_list.append((java_path, version))
        except PermissionError:
            print(f"Warning: Permission denied while accessing {path}")

    def run(self):
        self.search_dirs(self.path, 0)

    @staticmethod
    def check_java_version(java_path):
        try:
            result = subprocess.run([java_path, "-version"], capture_output=True, text=True, check=True)
            return ".".join(filter(None, search(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[._](\d+))?(?:-(.+))?",
                                                result.stderr.split('\n')[0]).groups()))
        except subprocess.CalledProcessError as e:
            logger.warning(f"Failed to execute {java_path} - {e}")
            return None
        except PermissionError:
            logger.warning(f"Permission denied while executing {java_path}")
            return None


def parallel_search_java(paths, priorities, strict, excluded_keywords, excluded_dirs, max_depth):
    result_list = []
    lock = threading.Lock()
    thread_pool = []

    # 初始化线程池
    for _ in range(len(paths)):
        thread = JavaSearchThread(None, result_list, lock, priorities, strict,
                                  excluded_keywords, excluded_dirs, max_depth, 5, thread_pool)
        thread_pool.append(thread)

    # 启动初始线程
    for path in paths:
        if thread_pool:
            thread = thread_pool.pop()
            thread.path = path
            thread.start()

    # 等待所有线程完成
    for thread in thread_pool:
        thread.join()

    return result_list


def get_search_paths():
    system = platform.system()
    if system == "Windows":
        paths = [f"{chr(65 + i)}:\\" for i in range(26) if os.path.exists(f"{chr(65 + i)}:\\")]
    elif system == "Darwin" or system == "Linux":
        paths = ["/usr", "/opt"]
    else:
        raise Exception("Unsupported operating system")

    return paths


plugin_info = PluginInfo(name="default_search_java", author="MojaveHao",
                         description="use threading to search java", version="0.1.0")
handler_info = EventHandlerInfo(name="default_search_java", author="MojaveHao", on=MSLXEvents.SearchJavaEvent)

PRIORITIES = []
EXCLUDED_KEYWORDS = []
EXCLUDED_DIRS = []
ONLY_SEARCH_KEYWORDS = False
MAX_SEARCH_DEPTH = None


def get_settings():
    with open(os.path.abspath(os.path.join(os.getcwd(), "Config/mslx.toml")), mode='r', encoding="utf-8") as f:
        logger.info("attempting to read config file")
        data = pytomlpp.load(f)
        conf = data.get("searching_java", {})
        global PRIORITIES
        PRIORITIES = conf.get("priorities", {})
        global ONLY_SEARCH_KEYWORDS
        ONLY_SEARCH_KEYWORDS = conf.get("only_keywords", False)
        global EXCLUDED_KEYWORDS
        EXCLUDED_KEYWORDS = conf.get("excluded_keywords", [])
        global EXCLUDED_DIRS
        EXCLUDED_DIRS = conf.get("excluded_dirs", [])
        global MAX_SEARCH_DEPTH
        MAX_SEARCH_DEPTH = conf.get("max_depth", MAX_SEARCH_DEPTH)
        logger.debug(f"高优先级关键字:{PRIORITIES}\n严格模式:{ONLY_SEARCH_KEYWORDS}\n排除的关键字:{EXCLUDED_KEYWORDS}\n"
                     f"排除的目录:{EXCLUDED_DIRS}\n最大搜索深度:{MAX_SEARCH_DEPTH}")


@AddPluginInfo(plugin_info=plugin_info)
def on_load():
    logger.info("default java searching plugin loaded")
    get_settings()


@EventHandler(info=handler_info)
def search_java():
    get_settings()
    result = {}  # {"path":..., "version":...}
    tmp = parallel_search_java(get_search_paths(), PRIORITIES, ONLY_SEARCH_KEYWORDS,
                               EXCLUDED_KEYWORDS, EXCLUDED_DIRS, MAX_SEARCH_DEPTH)
    for path, version in tmp:
        result[f"Java{version}({path})"] = {"path": path, "version": version}
    return result
